package com.sunlake.spring.main.common.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.BitSet;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具类
 * 封装Redis常用操作，包含解决缓存穿透、缓存击穿和缓存雪崩问题的方案
 * @author by liuhanzhi
 */
@Component
public class RedisUtils {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    // 用于解决缓存穿透问题的布隆过滤器
    private final BloomFilter bloomFilter;

    // 初始化布隆过滤器
    public RedisUtils() {
        // 初始化布隆过滤器，大小100000，哈希函数数量3
        this.bloomFilter = new BloomFilter(100000, 3);
    }

    // 设置缓存
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    // 设置缓存并指定过期时间
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    // 获取缓存
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    // 删除缓存
    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    // 判断缓存是否存在
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    // 设置过期时间
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    // 获取过期时间
    public Long getExpire(String key) {
        return redisTemplate.getExpire(key);
    }

    // 哈希操作 - 设置值
    public void hSet(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    // 哈希操作 - 获取值
    public Object hGet(String key, String hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }

    // 哈希操作 - 删除字段
    public void hDelete(String key, Object... hashKeys) {
        redisTemplate.opsForHash().delete(key, hashKeys);
    }

    // 列表操作 - 从左侧添加元素
    public Long lPush(String key, Object... values) {
        return redisTemplate.opsForList().leftPushAll(key, values);
    }

    // 列表操作 - 从右侧获取元素
    public Object rPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }

    // 集合操作 - 添加元素
    public Long sAdd(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }

    // 集合操作 - 获取所有元素
    public Object sMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }


    // 添加到布隆过滤器
    public void addToBloomFilter(String key) {
        bloomFilter.add(key);
    }

    // 检查是否可能存在于布隆过滤器
    public boolean mightContainInBloomFilter(String key) {
        return bloomFilter.mightContain(key);
    }

    // 带布隆过滤器的get方法，预防缓存穿透
    public Object getWithBloomFilter(String key, CacheLoader loader) {
        // 先检查布隆过滤器，如果肯定不存在，直接返回null
        if (!mightContainInBloomFilter(key)) {
            return null;
        }

        // 尝试从缓存获取
        Object value = get(key);
        if (value == null) {
            // 缓存未命中，从数据源加载
            value = loader.load();
            if (value != null) {
                // 数据源存在，则缓存并添加到布隆过滤器
                set(key, value);
                addToBloomFilter(key);
            }
        }
        return value;
    }

    // 解决缓存击穿问题 - 带过期时间的缓存获取
    // 使用双重检查锁定模式减少对数据库的并发访问
    public Object getWithLock(String key, long timeout, TimeUnit unit, CacheLoader loader) {
        // 尝试从缓存获取
        Object value = get(key);
        if (value != null) {
            return value;
        }

        // 定义锁的key
        String lockKey = "lock:" + key;

        // 尝试获取锁
        boolean locked = false;
        try {
            // 使用setIfAbsent实现分布式锁
            locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 30, TimeUnit.SECONDS);

            if (locked) {
                // 再次检查缓存，避免其他线程已经加载了数据
                value = get(key);
                if (value == null) {
                    // 从数据源加载
                    value = loader.load();
                    if (value != null) {
                        // 缓存数据并设置过期时间
                        set(key, value, timeout, unit);
                    }
                }
                return value;
            } else {
                // 获取锁失败，等待一段时间后重试
                Thread.sleep(100);
                return getWithLock(key, timeout, unit, loader);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        } finally {
            // 释放锁
            if (locked) {
                redisTemplate.delete(lockKey);
            }
        }
    }

    // 设置带随机过期时间的缓存，避免缓存雪崩
    // @param key 键
    // @param value 值
    // @param baseTimeout 基础过期时间
    // @param unit 时间单位
    // @param randomRange 随机范围(相对于baseTimeout的百分比)
    public void setWithRandomExpire(String key, Object value, long baseTimeout, TimeUnit unit, int randomRange) {
        // 计算随机过期时间
        long timeout = baseTimeout;
        if (randomRange > 0) {
            Random random = new Random();
            int variance = random.nextInt(randomRange * 2 + 1) - randomRange;
            timeout = baseTimeout + (baseTimeout * variance / 100);
        }

        // 设置缓存
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    // 简化版本，默认使用10%的随机范围
    public void setWithRandomExpire(String key, Object value, long baseTimeout, TimeUnit unit) {
        setWithRandomExpire(key, value, baseTimeout, unit, 10);
    }

    // 缓存加载器接口
    public interface CacheLoader {
        Object load();
    }

    // 布隆过滤器实现
    public class BloomFilter {
        private final BitSet bitset;
        private final int size;
        private final int hashCount;
        private final Random random;

        public BloomFilter(int size, int hashCount) {
            this.size = size;
            this.hashCount = hashCount;
            this.bitset = new BitSet(size);
            this.random = new Random();
        }

        public void add(String key) {
            for (int i = 0; i < hashCount; i++) {
                int hash = getHash(key, i);
                bitset.set(hash, true);
            }
        }

        public boolean mightContain(String key) {
            for (int i = 0; i < hashCount; i++) {
                int hash = getHash(key, i);
                if (!bitset.get(hash)) {
                    return false;
                }
            }
            return true;
        }

        private int getHash(String key, int seed) {
            random.setSeed(seed);
            int h = 0;
            byte[] data = key.getBytes();
            for (byte b : data) {
                h = 31 * h + (b & 0xFF) + random.nextInt(1000);
            }
            return Math.abs(h % size);
        }
    }
}